package com.derbysoft.lowcostadapter.utils;

import com.derbysoft.remote.core.validation.oval.OvalValidateSupport;
import com.derbysoft.remote.dswitch.generic.dto.PaymentCardDTO;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
import com.thoughtworks.xstream.converters.basic.DateConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.SerializableConverter;
import org.apache.commons.lang.Validate;
import org.dom4j.Document;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.SAXReader;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

import javax.xml.bind.helpers.AbstractMarshallerImpl;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.transform.Source;
import java.io.InputStream;
import java.util.HashMap;
import java.util.TimeZone;

/**
 * Created by: jason
 * Date: 2012-10-09
 */
public class XMLUtils {
    protected static Logger logger = LoggerFactory.getLogger(XMLUtils.class);
    private final XStream xStream = createXStream();

    @SuppressWarnings("unchecked")
    public XStream createXStream() {
        XStream xstream = new XStream();
        xstream.setMode(XStream.NO_REFERENCES);

        xstream.omitField(com.derbysoft.remote.core.validation.oval.OvalValidateSupport.class, "keepgoingValidate");
        xstream.omitField(com.derbysoft.remote.core.validation.oval.OvalValidateSupport.class, "checkCircularReference");
        xstream.omitField(OvalValidateSupport.class, "keepgoingValidate");
        xstream.omitField(OvalValidateSupport.class, "checkCircularReference");
        xstream.omitField(PaymentCardDTO.class, "cardNumber");
        xstream.omitField(PaymentCardDTO.class, "seriesCode");
        xstream.omitField(PaymentCardDTO.class, "expireDate");

        xstream.registerConverter(new DateConverter(TimeZone.getDefault()));
        xstream.registerConverter(new XMLGregorianCalendarConvert(), XStream.PRIORITY_VERY_HIGH);
        xstream.registerConverter(new SerializableConverter(xstream.getMapper(), xstream.getReflectionProvider(),null), XStream.PRIORITY_LOW + 2);
        xstream.registerConverter(new ReflectionConverter(xstream.getMapper(), xstream.getReflectionProvider()), XStream.PRIORITY_LOW + 1);
        return xstream;
    }

    public String toXml(Object object) {
        return xStream.toXML(object);
    }

    @SuppressWarnings("unchecked")
    public <T> T fromXml(String file, Class<T> clazz) {
        return (T) xStream.fromXML(getResourceAsStream(file));
    }

    public void assertEquals(String expected, String actual) {
        Assert.assertNotNull(expected);
        Assert.assertNotNull(actual);
        Assert.assertEquals(expected.replace(" ", "").replace("\r", "").replace("\n", ""),
                actual.replace(" ", "").replace("\r", "").replace("\n", ""));
    }

    protected InputStream getResourceAsStream(String file) {
        InputStream resourceAsStream = this.getClass().getResourceAsStream(file);
        if (resourceAsStream == null) {
            throw new IllegalArgumentException(String.format("File not found [%s] in package [%s]",
                    file, this.getClass().getPackage().getName()));
        }
        return resourceAsStream;
    }

    protected <T> T unmarshall(String file, Class<T> clazz) {
        Jaxb2Marshaller marshaller = createMarshaller(clazz);

        return unmarshall(file, clazz, marshaller);
    }

    @SuppressWarnings("unchecked")
    protected <T> T unmarshall(String file, Class<T> clazz, Jaxb2Marshaller marshaller) {
        try {
            Validate.notNull(file);
            Validate.notNull(marshaller);
            SAXReader reader = new SAXReader();
            Document document = reader.read(getResourceAsStream(file));
            Source source = new DocumentSource(document);

            return (T) marshaller.unmarshal(source);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new IllegalStateException(e.getMessage());
        }
    }

    private Jaxb2Marshaller createMarshaller(Class clazz) {
        String contextPath = clazz.getPackage().getName();

        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        HashMap<String, Object> map = new HashMap<String, Object>();

        map.put(AbstractMarshallerImpl.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setMarshallerProperties(map);
        marshaller.setContextPath(contextPath);
        try {
            marshaller.afterPropertiesSet();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return marshaller;
    }

    private static class XMLGregorianCalendarConvert extends AbstractSingleValueConverter {

        @Override
        public boolean canConvert(Class type) {
            return type.getGenericSuperclass() == XMLGregorianCalendar.class;
        }

        @Override
        public Object fromString(String str) {
            return null;
        }

        @Override
        public String toString(Object obj) {
            return obj.toString();
        }
    }
}
